package TangramBase;

import TangramCore.Database;
import TangramCore.Dilek;
import TangramCore.Hladina;
import TangramCore.HladinaEdit;
import TangramCore.HladinaObrys;
import TangramCore.Obrazec;
import TangramCore.ObrazecListener;
import TangramCore.Obrys;
import TangramCore.Point2D;
import TangramCore.Poloha;
import TangramCore.Skladacka;
import TangramCore.Vect;
import TangramCore.Vzhled;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;

public class TangramSolverFrame extends JFrame implements ActionListener, DocumentWindow, WindowStateListener, Database.DatabaseChangeListener, ObrazecListener {
	static final long serialVersionUID = 24362462L;
	
	private JMenuItem mniThis = null;
    private int iWindow = -1, lastState;
    
    TangramFrame frmDatabase;
    Database database;
    Database.ObrazecData od;
    Database.ObrazecData odSup = null;
    private int showed = -1;
    private boolean changed;
    Skladacka skl, sklSup;
    private String nazev = "New Figure";
    public static final String solverName = "Universal Solver";
    private static final Vzhled vDilky = Vzhled.getDefaultNahledDilek(), vDilkySel = Vzhled.getHidden(), vHelp = Vzhled.getDefaultHelp(); 
    Obrazec obrazec;
    HladinaObrys hVzor;
    HladinaEdit hSolve;
    Dilek dSolve[];
    ArrayList<Hladina> hladiny;
    boolean setup = false;
    
    private int dCount;
    private int order[];
    private boolean[] used;  //  used[i] tells whether piece # order[i] is already on the board
    private int numused;     // number of pieces currently on the board
    Stroke tolStroke;
    double tol;
    boolean tangram, novyObrys;
    
    private JLabel comment;
    private GameThread gameThread = null;   // a thread to run the puzzle solving procedure
    private JMenuItem restartAction;/*,restartClearAction,restartRandomAction*/;  // Menu items for user commands
    private JMenuItem goAction, pauseAction, stepAction, saveAction, quitAction, setNazev;
    //private JMenuItem oneSidedAction;
    private JCheckBoxMenuItem /*randomizePiecesChoice, checkForBlocksChoice, symmetryCheckChoice,*/ saveFirstChoice, saveAllChoice;
    
    private JRadioButtonMenuItem[] speedChoice = new JRadioButtonMenuItem[7]; // Menu items for setting the speed
    
    private final int[] speedDelay = {5, 25, 100, 500, 1000}; // delay times between moves for speeds 2--6
    
    volatile private int selectedSpeed = 4; // initial default speed and corresponding delay
    volatile private int delay = 100;
    
    public TangramSolverFrame(TangramFrame tf, Database d){
        frmDatabase = tf;
        database = d;
        skl = database.getActSet();
        sklSup = skl.getSuperSet();
        od = database.getDefaultObrazecCopy();//new Database.ObrazecData(skl,dSolve = skl.getDilky());
        dSolve = od.d;
        if(sklSup != null)
            odSup = database.getDefaultObrazecCopy(sklSup.nazev);
        hladiny = new ArrayList<Hladina>(4);
        novyObrys = true;
        setTitle();
        
        hVzor = new HladinaObrys(dSolve,od.o,vDilky,vHelp,null, Hladina.DEFAULT,false,new Rectangle(0,0,-100,-100));
        hSolve = new HladinaEdit(dSolve,od.o,vDilky,vDilkySel,vHelp,null,new Rectangle(0,0,-100,-100));
        //hSetup = new HladinaEdit(dSolve,od.o,TangramEditFrame.vDilky[TangramEditFrame.iVzhled],TangramEditFrame.vDilkySel[TangramEditFrame.iVzhled],TangramEditFrame.vObrys[TangramEditFrame.iVzhled],null,new Rectangle(0,0,-100,-100));
        hladiny.add(hVzor);
        hladiny.add(hSolve);
        //hladiny.add(hSetup);
        obrazec = new Obrazec(skl,Color.white,hladiny,null,null,null, null);
        Dilek.vyberVse(dSolve, true);
        hVzor.prepniVzhled(Hladina.VZHLED_OBRYS);
        hSolve.prepniVzhled(Hladina.VZHLED_DILKY);

        dCount = dSolve.length;
        order = new int[dCount];
        used = new boolean[dCount];
        Container pane = getContentPane();
        pane.setLayout(new BorderLayout(0,0));
        pane.add(obrazec, BorderLayout.CENTER);
        
        comment = new JLabel("", JLabel.CENTER);
        comment.setFont(new Font("TimesRoman", Font.BOLD, 14));
        pane.add(comment, BorderLayout.SOUTH);
        
        JPanel right = new JPanel();                // holds control buttons
        right.setLayout(new GridLayout(6,1,5,5));
        restartAction = new JMenuItem("Restart");
        //restartClearAction = new JMenuItem("Restart / Empty Board");
        //restartRandomAction = new JMenuItem("Restart / Random");
        goAction = new JMenuItem("Go");
        pauseAction = new JMenuItem("Pause");
        stepAction = new JMenuItem("Step");
        saveAction = new JMenuItem("Save Image...");
        quitAction = new JMenuItem("Quit");
        //randomizePiecesChoice = new JCheckBoxMenuItem("Randomize Order of Pieces");
        //checkForBlocksChoice = new JCheckBoxMenuItem("Check for Obvious Blocking",true);
        //symmetryCheckChoice = new JCheckBoxMenuItem("Symmetry Check",true);
        saveFirstChoice = new JCheckBoxMenuItem("Save First Solution",true);
        saveAllChoice = new JCheckBoxMenuItem("Save All Solutions", true);
        setNazev = new JMenuItem("Set Figure Name...");
        //oneSidedAction  = new JMenuItem("One Sided [Currently OFF]...");
        
        String commandKey;
        commandKey = "control ";
        try {
            String OS = System.getProperty("os.name");
            if (OS.startsWith("Mac"))
                commandKey = "meta ";
        } catch (Exception e) {
        }
        
        restartAction.addActionListener(this);
        //restartClearAction.addActionListener(this);
        //restartRandomAction.addActionListener(this);
        goAction.addActionListener(this);
        pauseAction.addActionListener(this);
        stepAction.addActionListener(this);
        saveAction.addActionListener(this);
        quitAction.addActionListener(this);
        //randomizePiecesChoice.addActionListener(this);
        //checkForBlocksChoice.addActionListener(this);
        //symmetryCheckChoice.addActionListener(this);
        saveFirstChoice.addActionListener(this);
        saveAllChoice.addActionListener(this);
        setNazev.addActionListener(this);
        //oneSidedAction.addActionListener(this);
        goAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "G"));
        pauseAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "P"));
        stepAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "S"));
        restartAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "R"));
        //restartClearAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "E"));
        //restartRandomAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "D"));
        quitAction.setAccelerator(KeyStroke.getKeyStroke(commandKey + "Q"));
        
        ButtonGroup group = new ButtonGroup();
        speedChoice[0] = new JRadioButtonMenuItem("Fastest");
        speedChoice[1] = new JRadioButtonMenuItem("Very Fast");
        speedChoice[2] = new JRadioButtonMenuItem("Faster");
        speedChoice[3] = new JRadioButtonMenuItem("Fast");
        speedChoice[4] = new JRadioButtonMenuItem("Moderate");
        speedChoice[5] = new JRadioButtonMenuItem("Slow");
        speedChoice[6] = new JRadioButtonMenuItem("Slower");
        for (int i = 0; i < 7; i++) {
            group.add(speedChoice[i]);
            speedChoice[i].addActionListener(this);
            speedChoice[i].setAccelerator(KeyStroke.getKeyStroke(commandKey + (char)('0' + i)));
        }
        speedChoice[4].setSelected(true);
        
        gameThread = new GameThread();
        gameThread.solutionListener = frmDatabase;
        
        JMenuBar bar = getMenuBar(true,frmDatabase != null, null);
        //bar.add(makeSizeMenu());
        setJMenuBar(bar);
        pack();
        
        int w = 700, h = 700;
        setSize(w,h);
        TangramFrame.centerOnScreen(this);
        //setResizable(false);
        
        //addKeyListener(tf);
        //setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                pauseAction.doClick();
                e.getWindow().setVisible(true);
            }
        });
        addWindowListener(new java.awt.event.WindowAdapter() {public void windowClosing(WindowEvent e){showDocument(false);}});
        
        
        obrazec.setSize(getWidth(),getHeight());
        obrazec.addObrazecListener(this);
        
        /*if(false) {
            gameThread.setMessage(RESTART_RANDOM_MESSAGE);
            pauseAction.setEnabled(true);
            stepAction.setEnabled(false);
            //creatingBoard = false;
        } else */{
            gameThread.setMessage(WAIT_MESSAGE);
            pauseAction.setEnabled(false);
            /*creatingBoard = spareSpaces > 0;
                      if (creatingBoard)
               comment.setText("Select Squares or Use \"Go\" to Start");
                      else*/
            comment.setText("Use \"Go\" to Start");
        }
        gameThread.start();
        //obrazec.vycentruj();
        //obrazec.addObrazecListener(this);
    }

    private void askForName(){
        nazev = JOptionPane.showInputDialog(this,"Please specify a name of the figure being solved:",nazev);
        gameThread.nazev = nazev;
        setTitle();
    }
    
    private boolean restart(){
        if(gameThread.paused && JOptionPane.showConfirmDialog(this,"The solver is solving another figure, is paused and can be resumed. Do you want to abort it?","Abort",JOptionPane.YES_NO_OPTION)!=JOptionPane.YES_OPTION)
            return false;
        else if(gameThread.running && JOptionPane.showConfirmDialog(this,"The solver is solving another figure. Do you want to abort it?","Abort",JOptionPane.YES_NO_OPTION)!=JOptionPane.YES_OPTION)
            return false;
        gameThread.setMessage(RESTART_MESSAGE);
        pauseAction.setEnabled(false);
        stepAction.setEnabled(false);
        return true;
    }
    
    public boolean quit(){
        if(isVisible()){
            quitAction.doClick();
            return true;//System.out.println("quit clicked");
        }
        return false;
    }
    public void pause(){
        //System.out.println("pause");
        pauseAction.setEnabled(false);
        stepAction.setEnabled(true);
        gameThread.setMessage(PAUSE_MESSAGE);
    }
    
    public void actionPerformed(ActionEvent evt) {
        Object source = evt.getSource();
        if (source == restartAction) {
            pauseAction.setEnabled(false);
            stepAction.setEnabled(false);
            gameThread.setMessage(RESTART_MESSAGE);
        }
            /*else if (source == restartClearAction) {
          pauseAction.setEnabled(false);
          stepAction.setEnabled(false);
          gameThread.setMessage(RESTART_CLEAR_MESSAGE);
                   } else if (source == restartRandomAction) {
          pauseAction.setEnabled(false);
          stepAction.setEnabled(false);
          gameThread.setMessage(RESTART_RANDOM_MESSAGE);
                   }*/
        else if (source == goAction) {
            pauseAction.setEnabled(true);
            stepAction.setEnabled(false);
            gameThread.setMessage(GO_MESSAGE);
        } else if (source == pauseAction) {
            pause();
        } else if (source == stepAction) {
            gameThread.setMessage(STEP_MESSAGE);
        }
        /*else if (source == checkForBlocksChoice)
          gameThread.checkForBlocks = checkForBlocksChoice.isSelected();
                   else if (source == randomizePiecesChoice)
          gameThread.randomizePieces = randomizePiecesChoice.isSelected();
                   else if (source == symmetryCheckChoice)
          gameThread.symmetryCheck = symmetryCheckChoice.isSelected();*/
        else if (source == saveFirstChoice)
            gameThread.saveFirst = saveFirstChoice.isSelected();
        else if (source == saveAllChoice)
            gameThread.saveAll = saveAllChoice.isSelected();
        else if (source == setNazev)
            askForName();
        /*else if (source == oneSidedAction)
            doOneSidedCommand();
                   else if (source == saveAction)
            doSaveImage();*/
        else if (source == quitAction){
            if(gameThread.solutionListener == null)
                System.exit(0);
            else {
                pause();
                showDocument(false);
            }
        } else if (source instanceof JRadioButtonMenuItem) {
            JRadioButtonMenuItem item = ((JRadioButtonMenuItem) source);
            int i;
            for (i = 0; i < speedChoice.length; i++) {
                if (speedChoice[i] == item)
                    break;
            }
            if (i == speedChoice.length || i == selectedSpeed)
                return;
            selectedSpeed = i;
            if (selectedSpeed < 2)
                delay = 0;
            else
                delay = speedDelay[selectedSpeed - 2];
            //if (gameThread.running)
            //    board.setAutopaint(selectedSpeed > 1);
            obrazec.update();
            gameThread.doDelay(25);
        }
    }
    
    private final static int GO_MESSAGE = 1;      // the values for the message variable
    private final static int STEP_MESSAGE = 2;
    private final static int PAUSE_MESSAGE = 3;
    private final static int RESTART_MESSAGE = 4;
    private final static int RESTART_CLEAR_MESSAGE = 5;
    //private final static int RESTART_RANDOM_MESSAGE = 6;
    private final static int WAIT_MESSAGE = 7;
    private final static int TERMINATE_MESSAGE = 8;
    
    private class GameThread extends Thread {  // This represents the thread that solves the puzzle.
        int moveCount;        // How many pieces have been placed so far
        int movesSinceCheck;  // How many moves since the last time the board was redrawn, while running at speed #1
        int solutionCount;    // How many solutions have been found so far
        
        boolean flipSymetry, rotationSymetry, uniqueFlip;
        double rotationSymetryAngle, uniqueAngle;
        
        volatile boolean running = false, paused = false;   // True when the solution process is running (and not when it is paused)
        
        boolean aborted;  // used in play() to test whether the solution process has been aborted by a "restart"
        
        volatile int message = 0;  // "message" is used by user-interface thread to send control messages
        // to the game-playing thread.  A value of  0 indicates "no message"
        volatile boolean saveFirst;        // If true, the first solution will be passed to SolutionListener
        volatile boolean saveAll;          // If true, all solutions will be passed to SolutionListener
        
        volatile SolutionListener solutionListener; //this will recieve solutions
        volatile String nazev;
        
        List<Poloha[]> solutions;
        
        private final Runnable submitSolution = new Runnable() {
            public void run(){
                solutionListener.solutionFound(nazev,solutionCount, skl.getDilky(dSolve));
            }
        };
        
        private final Runnable updateObrazec = new Runnable() {
            public void run(){
                obrazec.update();
            }
        };
        
        private void update(){
            try{
                SwingUtilities.invokeAndWait(updateObrazec);
            } catch(Exception ex){}
        }
        
        @Override
        public void run(){
            while (true) {
                running = false;
                paused = false;
                while (message != GO_MESSAGE && message != TERMINATE_MESSAGE) {  // wait for game setup
                    if (message == RESTART_CLEAR_MESSAGE || message == RESTART_MESSAGE) {
                        Dilek.copyFrom(dSolve,skl.getDilky());
                        Dilek.vyberVse(dSolve,true);
                        comment.setText("Use \"Go\" to Start");
                        update();
                    }
                    //else if(message == WAIT_MESSAGE || message == PAUSE_MESSAGE){
                    setMessage(0);
                    if(solutionListener != null)
                        solutionListener.solverNotRunning();
                    doDelay( -1); // wait forever (for control message to start game)
                    //}
                }
                if (message == TERMINATE_MESSAGE)
                    break;
                //creatingBoard = false;
                running = true;
                paused = false;
                saveAction.setEnabled(false);
                //board.setAutopaint(delay > 0);
                doDelay(25);
                // begin next game
                pauseAction.setEnabled(true);
                stepAction.setEnabled(false);
                comment.setText("Solving...");
                message = 0;
                for (int i=0; i<dCount; i++){
                    //if (randomizePieces)
                    order[i] = i;  //TODO pripadne randomize order
                    used[i] = false;
                }
                numused = 0;
                Dilek.vyberVse(dSolve, true);
                //obrazec.update();
                moveCount = movesSinceCheck = solutionCount = 0;
                //board.setAutopaint( selectedSpeed > 1 );
                //randomizePiecesChoice.setEnabled(false);
                //symmetryCheckChoice.setEnabled(false);
                saveFirstChoice.setEnabled(false);
                saveAllChoice.setEnabled(false);
                setNazev.setEnabled(false);
                aborted = false;
                tol = Obrys.TOLERANCE_AREA;
                tolStroke = od.o.getToleranceStroke();
                Area a = (Area)od.o.clone();
                a.subtract(new Area(tolStroke.createStrokedShape(a)));//new Area(d));
                tangram = skl.nazev.toLowerCase().equals("tangram");
                solutions = new ArrayList<Poloha[]>();
                novyObrys = false;
                flipSymetry = false; //asi zbytecne
                rotationSymetry = false; //asi zbytecne
                try {
                    play(new Obrys(a)); // run the recursive algorithm that will solve the puzzle
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (message == TERMINATE_MESSAGE)
                    break;
                //randomizePiecesChoice.setEnabled(true);
                //symmetryCheckChoice.setEnabled(true);
                //oneSidedAction.setEnabled(true);
                saveFirstChoice.setEnabled(true);
                saveAllChoice.setEnabled(true);
                setNazev.setEnabled(true);
                running = false;
                paused = false;
                if(solutionListener != null)
                    solutionListener.solverNotRunning();
                saveAction.setEnabled(true);
                //board.setAutopaint(true);
                //obrazec.update();
                if (!aborted) {
                    pauseAction.setEnabled(false);
                    stepAction.setEnabled(false);
                    if (solutionCount == 0)
                        comment.setText("Done. No soutions. " + moveCount + " moves.");
                    else if (solutionCount == 1)
                        comment.setText("Done. 1 solution. " + moveCount + " moves.");
                    else
                        comment.setText("Done. " + solutionCount + " solutions. "+ moveCount + " moves.");
                    doDelay(-1);
                    //System.out.println("message = " + message);
                }
                if (message == TERMINATE_MESSAGE)
                    break;
            }
        }
        
        private int addSolution(Dilek[] dSol){//returns either -1 or index of duplicate solution
            Dilek d [] = skl.getDilky(dSol);
            for(int i=0; i<solutions.size(); i++){
                Poloha psol[] = solutions.get(i);
                if(skl.isSameSolution(psol, d, Obrys.TOLERANCE_AREA, Obrys.TOLERANCE_UHEL_RAD))
                    return i;
            }
            Poloha pnew[] = new Poloha[dSol.length];
            for(int i=0; i < dSol.length; i++){
                dSol[i].normalize();
                pnew[i] = new Poloha(dSol[i].getPoloha());
                //System.out.println(pnew[i]);
            }
            solutions.add(pnew);
            return -1;
        }
        private double[] getShortestSideLeft(){
            double ret[] = new double[2];
            ret[0] = ret[1] = Double.MAX_VALUE;
            for(int i=0; i<dSolve.length; i++){
                if(used[i])
                    continue;
                Dilek.Typ typ = dSolve[order[i]].typ;
                double d = typ.sidesSortedLength[0/*typ.sidesSortedLength.length-1*/].length;
                if(d < ret[0])
                    ret[0] = d;
                d = typ.sidesSortedAngle[typ.sidesSortedAngle.length-1].angle;
                if(d < ret[1])
                    ret[1] = d;
            }
            return ret;
        }
        
        void play(Obrys oLeft) throws Exception {
            Obrys.ObrysData odLeft = oLeft.od;
            
            if(numused == 0){
                Dilek.Typ typ = new Dilek.Typ(0, "", odLeft.nSegs - 1, 1);
                Obrys.compare(odLeft, odLeft, Obrys.TOLERANCE_AREA, typ);
                flipSymetry = typ.flipSymetry;
                rotationSymetryAngle = typ.rotationSymetryAngle;
                rotationSymetry = rotationSymetryAngle < 2*Math.PI;
            }
            
            int iToBePlaced, lastPlacedType = -1;
            //Dilek.Side preferredSides[] = null;
            for(iToBePlaced=0; iToBePlaced < dCount; iToBePlaced++){
                if (used[iToBePlaced])
                    continue;
                //Dilek firstToBePlaced = dSolve[order[iToBePlaced]];
                //preferredSides = firstToBePlaced.typ.sidesSortedLength;
                break;
            }
            
            Point2D p1 = odLeft.segCoords[0], p2;
            //double d , max = 0.0, maxAngle = 0.0, pref = max, prefAngle = maxAngle;
            double d , max = Double.MAX_VALUE, maxAngle = 0, minAngle = 2*Math.PI;//, pref = max, prefAngle = maxAngle;
            int maxi = 0;//, prefi = maxi, prefSide = preferredSides.length;
            boolean preferSide = false;
            for(int i = 1; i<odLeft.nSegs; i++){
                if(odLeft.segType[i] ==  PathIterator.SEG_MOVETO){
                    p1 = odLeft.segCoords[i];
                    continue;
                }
                p2 = odLeft.segCoords[i];
                
                double ina = odLeft.insideAngle[i - 1];
                if(ina > Math.PI){
                    p1 = p2;
                    continue;
                }
                d = p2.distance(p1);
                if(odLeft.insideAngle[i] < Math.PI){
                    if (!preferSide || d < max){
                        preferSide = true;
                        maxi = i - 1;
                        max = d;
                    }
                }
                if(!preferSide && ina < minAngle){
                    max = d;
                    maxi = i - 1;
                    minAngle = ina;
                }
                p1 = p2;
            }
            
            Point2D po1[] = new Point2D[4];
            Point2D po2[] = new Point2D[4];
            po1[0] = po1[1] = odLeft.segCoords[maxi];
            po2[0] = po2[1] = odLeft.segCoords[maxi+1];
            po1[2] = po1[3] = odLeft.segCoords[maxi+1];
            po2[2] = po2[3] = odLeft.segCoords[maxi];
            
            //odLeft.segCoords[maxi], odLeft.segCoords[maxi+1] => nejdelsi strana
            for(; iToBePlaced < dCount; iToBePlaced++){
                if (used[iToBePlaced])
                    continue;
                Dilek dToBePlaced = dSolve[order[iToBePlaced]];
                int typeToBePlaced = dToBePlaced.typ.id;
                if(lastPlacedType == typeToBePlaced)
                    continue;
                lastPlacedType = typeToBePlaced;
                used[iToBePlaced] = true;
                dToBePlaced.vyber(false);
                double shortestSideLeft = Double.NaN, smallestAngleLeft = Double.NaN;
                
                numused++;
                Dilek dBackup = null;
                
                flip: for(int f=0; f<2; f++) {
                    if(f == 1 && dToBePlaced.typ.flipSymetry)
                        continue flip;
                    if(dToBePlaced.poradi == skl.needsFlip && solutionCount>0 && flipSymetry &&
                            (f == 0 ? dToBePlaced.getPoloha().flip : !dToBePlaced.getPoloha().flip) != uniqueFlip)
                        continue flip;
                    if (f == 1) {
                        dBackup = new Dilek(dToBePlaced);
                        dToBePlaced.flip(null);
                    }
                    Dilek dFlippedBackup = new Dilek(dToBePlaced);
                    
                    java.awt.geom.Rectangle2D r1, r2;
                    Point2D pd1[] = new Point2D[4];
                    Point2D pd2[] = new Point2D[4];
                    
                    Dilek.Side side[] =  dToBePlaced.getSortedSides();
                    for (int i = 0; i < side.length; i++) {
                        p1 = side[i].p1;
                        p2 = side[i].p2;
                        double sideLength = side[i].length;// p2.getDistanceTo(p1);
                        
                        pd1[0] = pd1[3] = p1;
                        pd2[0] = pd2[3] = p2;
                        pd1[1] = pd1[2] = p2;
                        pd2[1] = pd2[2] = p1;
                        
                        int posCount = 2; //Math.abs(sideLength - max) < tol ? 2 : 4;
                        //posCount = 4;
                        for (int p = 0; p < posCount; p++) {
                            maxAngle = Math.asin((po2[p].y - po1[p].y) / max); // longest oLeft side angle
                            if ((po2[p].x - po1[p].x) < 0)
                                maxAngle = Math.PI - maxAngle;
                            
                            d = Math.asin((pd2[p].y - pd1[p].y) / sideLength); //dilek side angle
                            if ((pd2[p].x - pd1[p].x) < 0)
                                d = Math.PI - d;
                            
                            //dToBePlaced.otoc(pd1[p], maxAngle - d);          //v tomto poradi nelze pokud se provadi normalizace
                            //dToBePlaced.posun(Vect.vect(pd1[p], po1[p]));    //otoceni pri kazdem otoceni <= otoc zmeni pozici pd1[p]
                            
                            dToBePlaced.posun(Vect.vect(pd1[p], po1[p]));      //takto funguje vzdy, po1[p] se otocenim dilku nezmeni
                            dToBePlaced.otoc(po1[p], maxAngle - d);
                            boolean validRotation = true;
                            if(rotationSymetry && solutionCount>0 && dToBePlaced.poradi == skl.uniqueDilek){
                                double otoceni = dToBePlaced.getPoloha().otoceni;
                                if(otoceni < (uniqueAngle - Obrys.TOLERANCE_UHEL_RAD) || otoceni >= (uniqueAngle + rotationSymetryAngle - Obrys.TOLERANCE_UHEL_RAD))
                                    validRotation = false;
                            }
                            //obrazec.update();
                            
                            r1 = odLeft.a.getBounds2D();
                            r2 = dToBePlaced.getBounds2D();
                            
                            if (validRotation && Vect.liesInside(r1, r2, tol)) { //bounding rectangles overlap
                                if (odLeft.aStrokedCache == null ||
                                        odLeft.cacheTol != tol) {
                                    odLeft.aStrokedCache = new Area(tolStroke.createStrokedShape(odLeft.a)); ;
                                    odLeft.aStrokedCache.add(odLeft.a);
                                    odLeft.cacheTol = tol;
                                }
                                Area aPlaced = new Area(dToBePlaced);
                                aPlaced.subtract(odLeft.aStrokedCache);
                                if (aPlaced.isEmpty()) { //dilek nevycniva
                                    update();
                                    boolean stepping = false;
                                    if (message > 0) { // test for "messages" generated by user actions
                                        if (message == PAUSE_MESSAGE || message == STEP_MESSAGE) {
                                            stepping = true;
                                            if (running && delay == 0)
                                                update();
                                            running = false;
                                            paused = true;
                                            saveAction.setEnabled(true);
                                            setMessage(0);
                                        } else if (message >= RESTART_MESSAGE) {
                                            aborted = true;
                                            return; // note: don't setMessage(0), since run() has to handle message
                                        } else { // go message
                                            running = true;
                                            paused = false;
                                            saveAction.setEnabled(false);
                                            //board.setAutopaint(selectedSpeed > 1);
                                            comment.setText("Solving...");
                                            setMessage(0);
                                        }
                                    }
                                    
                                    Area aLeft = (Area) odLeft.a.clone();
                                    aLeft.subtract(new Area(dToBePlaced));
                                    Area strokeSub = new Area(tolStroke.createStrokedShape(aLeft));
                                    aLeft.subtract(strokeSub);
                                    try{
                                        aLeft = new Obrys(aLeft);
                                    } catch (InternalError ex){
                                        ex.printStackTrace();
                                    }
                                    String message;// = new String("All pieces placed: ");
                                    moveCount++;
                                    if (aLeft.isEmpty()) {
                                        if (numused == dCount){
                                            if(solutionCount == 0){
                                                uniqueAngle = dSolve[skl.uniqueDilek].getPoloha().otoceni;
                                                uniqueFlip = dSolve[skl.needsFlip].getPoloha().flip;
                                            }
                                            int sol = addSolution(dSolve);
                                            if(sol == -1){
                                                solutionCount++;
                                                message = "Solution #" + solutionCount + "... (" + moveCount + " moves)";
                                                System.out.println(message);
                                                comment.setText(message);
                                                if(solutionListener != null){
                                                    if (saveAll || (saveFirst && solutionCount == 1)) {
                                                        try {
                                                            SwingUtilities.invokeAndWait(submitSolution);
                                                        } catch (Exception ex) {}
                                                    }
                                                }
                                                doDelay(2000);
                                            } else {
                                                message = "Duplicate of Solution #" + (sol + 1) + "... (" + moveCount + " moves, " + solutionCount + " solutions)";
                                                System.out.println(message);
                                                //comment.setText(message);
                                            }
                                        } else
                                            throw new Exception("Solution found and not all pieces used!");
                                    } else {
                                        if (numused == dCount)
                                            throw new Exception("All pieces placed and solution not found!");
                                        else{
                                            if (stepping) { // pause after placing a piece
                                                comment.setText("Paused." + " (" + moveCount + " moves, " + solutionCount + " solutions)");
                                                if(solutionListener != null)
                                                    solutionListener.solverNotRunning();
                                                doDelay( -1); // wait indefinitly for command
                                            } else if (delay > 0)
                                                doDelay(delay);
                                            if (movesSinceCheck >= 10 && !stepping) {
                                                if (selectedSpeed == 1) {
                                                    //update(); // At speed 1, board.autopaint is false; force a redraw every 1000 moves
                                                    doDelay(20);
                                                }
                                                movesSinceCheck = 0;
                                            }
                                            aLeft.add(new Area(tolStroke.createStrokedShape(aLeft)));
                                            //moveCount++;
                                            Obrys oNextLeft = null;
                                            try {
                                                oNextLeft = new Obrys(aLeft);
                                            } catch (InternalError ex) {
                                                ex.printStackTrace();
                                            }
                                            if(Double.isNaN(shortestSideLeft)){
                                                double ret[] = getShortestSideLeft();
                                                shortestSideLeft = ret[0] - Obrys.TOLERANCE_AREA;
                                                smallestAngleLeft = ret[1] - Obrys.TOLERANCE_UHEL_RAD;
                                            }
                                            if(!oNextLeft.od.hasIncompatibleSide(shortestSideLeft, smallestAngleLeft)){
                                                play(oNextLeft);
                                                if (aborted)
                                                    return;
                                            }
                                        }
                                    }
                                }
                            }
                            dToBePlaced.copyFrom(dFlippedBackup);
                        }
                        p1 = p2;
                    }
                }
                used[iToBePlaced] = false;
                dToBePlaced.vyber(true);
                if(dBackup != null)
                    dToBePlaced.copyFrom(dBackup);
                numused--;
            }
        }
        
        synchronized void doDelay(int milliseconds) {
            if (milliseconds < 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            } else {
                try {
                    wait(milliseconds);
                } catch (InterruptedException e) {
                }
            }
            
        }
        
        synchronized void setMessage(int message) { // send control message to game thread
            this.message = message;
            if (message > 0)
                notify(); // wake game thread if it is sleeping or waiting for a message (in the doDelay method)
        }
    }
    
    public JMenuBar getMenuBar(boolean includeSaveAndQuit, boolean hasListener, TangramSolverFrame getOptionsFromThisOne) {
        JMenuBar bar = new JMenuBar();
        JMenu control = new JMenu("Control");
        control.add(goAction);
        control.add(pauseAction);
        control.add(stepAction);
        control.addSeparator();
        control.add(restartAction);
        if (includeSaveAndQuit) {
            control.addSeparator();
            control.add(saveAction);
            control.addSeparator();
            if(hasListener){
                control.add(setNazev);
                control.add(saveFirstChoice);
                control.add(saveAllChoice);
                control.addSeparator();
            }
            control.add(quitAction);
        }
        bar.add(control);
        JMenu speed = new JMenu("Speed");
        speed.add(speedChoice[0]);
        speed.addSeparator();
        for (int i = 1; i < speedChoice.length; i++)
            speed.add(speedChoice[i]);
        bar.add(speed);
        if (getOptionsFromThisOne != null) {
         /*gameThread.randomizePieces = getOptionsFromThisOne.randomizePiecesChoice.isSelected();
         randomizePiecesChoice.setSelected(gameThread.randomizePieces);
         gameThread.checkForBlocks = (getOptionsFromThisOne.checkForBlocksChoice.isSelected());
         checkForBlocksChoice.setSelected(gameThread.checkForBlocks);
         if (rows*cols >= 60) {
            gameThread.symmetryCheck = getOptionsFromThisOne.symmetryCheckChoice.isSelected();
            symmetryCheckChoice.setSelected(gameThread.symmetryCheck);
         }*/
            gameThread.nazev = nazev = getOptionsFromThisOne.nazev;
            gameThread.saveFirst = getOptionsFromThisOne.saveFirstChoice.isSelected();
            saveFirstChoice.setSelected(gameThread.saveFirst);
            gameThread.saveAll = getOptionsFromThisOne.saveAllChoice.isSelected();
            saveAllChoice.setSelected(gameThread.saveAll);
         /*gameThread.useOneSidedPieces = getOptionsFromThisOne.gameThread.useOneSidedPieces;
         if (gameThread.useOneSidedPieces)
            oneSidedAction.setText("One Sided [Currently ON]...");
         gameThread.useSideA = getOptionsFromThisOne.gameThread.useSideA;*/
            for (int i = 0; i < speedChoice.length; i++)
                if (getOptionsFromThisOne.speedChoice[i].isSelected()) {
                speedChoice[i].setSelected(true);
                selectedSpeed = i;
                if (selectedSpeed < 2)
                    delay = 0;
                else
                    delay = speedDelay[selectedSpeed-2];
                break;
                }
        } else{
          /*gameThread.checkForBlocks = checkForBlocksChoice.isSelected();
          gameThread.randomizePieces = randomizePiecesChoice.isSelected();
          gameThread.symmetryCheck = symmetryCheckChoice.isSelected();*/
            gameThread.saveFirst = saveFirstChoice.isSelected();
            gameThread.saveAll = saveAllChoice.isSelected();
        }
        return bar;
    }
    
    public void show(int i, Database.ObrazecData odNew){
        if(!restart())
            return;
        showed = i;
        
        if(odNew == null)
            odNew = od;
        if(i > -1)
            odNew = database.getObrazecData(showed);
        if(odNew != null)
            od = odNew;
        if(od == null)
            od = database.getDefaultObrazec();
        nazev = od.nazev;
        novyObrys = true;
        setTitle();
        gameThread.nazev = od.nazev;
        dSolve = skl.getDilky();
        hVzor.update(dSolve, od.o);
        hSolve.update(dSolve, od.o);
        dCount = dSolve.length;
        order = new int[dCount];
        used = new boolean[dCount];
        Dilek.vyberVse(dSolve, true);
        obrazec.update();
    }
    
    private void setTitle(){
        setTitle(solverName + sDash + skl.nazev + sLFigureBracket + nazev + (changed ? sChanged : sUpToDate) + sRFigureBracket);
        setMenuItemText();
    }
    
    private void showNext(){
        if(showed < -1){
            show(0, null);
            return;
        }
        if(showed < -1 || showed > database.getSize()-2)
            return;
        show(showed + 1, null);
    }

    private void showPrev(){
        if(showed < 1)
            return;
        show(showed - 1, null);
    }

    public void restoreState(){
        setExtendedState(lastState);
        toFront();
    }
    
    private void setMenuItemText(){
        if(mniThis != null)
            mniThis.setText(sLNumberBracket + iWindow + /*(changed ? sChanged : sUpToDate) + */sRNumberBracket + getTitle());
    }
    
    public void setMenuItem(int i, JMenuItem mni){
        iWindow = i;
        mniThis = mni;
        setMenuItemText();
    }
    
    public void windowStateChanged(WindowEvent ev){
        if((ev.getNewState() & JFrame.ICONIFIED) != 0)
            lastState = ev.getOldState();
    }
    
    public boolean showDocument(boolean jak){
        boolean ret = jak ? frmDatabase.windowOpened(this):frmDatabase.windowClosed(this);
        setVisible(jak);
        if(jak)
            toFront();
        else
            setSetupMode(false);
        return ret;
    }
    public boolean databaseChanged(int what, int i){
        if(showed < 0)
            return true;
        //boolean myChange = i < 0 || i == showed;
        if(i < 0)
            showed = database.getIndex(od);
        setTitle();
        return true;
    }
    public boolean hasChanged(){
        return false;
    }
    public void keyDown(KeyEvent e){
        if(gameThread.running || setup)
            return;
        switch(e.getKeyCode()){
            case KeyEvent.VK_PAGE_DOWN:
                showNext();
                break;
            case KeyEvent.VK_PAGE_UP:
                showPrev();
                break;
        }
    }
    
    private void setSetupMode(boolean jak){
        if(setup == jak)
            return;
        if(jak == false){
            hSolve.setVzhled(vDilky,vDilkySel,vHelp);   
            if(showed > -1){
                od = new Database.ObrazecData(sklSup,odSup.d, odSup.o.od);
                showed = -1;
                nazev = od.nazev;
                setTitle();
            }
            else
                od.o = odSup.o;
            hSolve.update(dSolve,od.o);
            obrazec.setHladinaEdit(null);
            obrazec.setSkladacka(skl,null,null,null);
            hVzor.update(dSolve,odSup.o);
            hVzor.show();
            obrazec.vyberVse(false);
            goAction.setEnabled(true);
        }
        else {
            if(!restart())
                return;
            goAction.setEnabled(false);                  
            hVzor.hide();
            hSolve.setVzhled(TangramEditFrame.vDilky[TangramEditFrame.iVzhled],TangramEditFrame.vDilkySel[TangramEditFrame.iVzhled],TangramEditFrame.vObrys[TangramEditFrame.iVzhled]);
            obrazec.setHladinaEdit(hSolve);
            obrazec.setSkladacka(sklSup,odSup.d,null,odSup.o);
            obrazec.update();

        }
        setup = jak;
    }
    
    public void mouseDown(MouseEvent e){
        if(sklSup == null || gameThread.running)
            return;
        if(e.getButton() == MouseEvent.BUTTON3)
            setSetupMode(!setup);
    }
    public void changed(){
    
    }
}
